import os
import glob
import customtkinter as ctk
from tkinter import filedialog, messagebox
from pptx import Presentation
from pptx.util import Inches
from PIL import Image
import sys
import subprocess
import threading

class PhotoToPPTApp(ctk.CTk):
    def __init__(self):
        super().__init__()

        self.title("图片转PPT工具 - 现代版")
        self.geometry("900x800")  # 增加窗口尺寸以确保内容完全显示
        
        # 设置窗口居中显示
        self.center_window()
        
        # 设置最小窗口尺寸，防止内容被压缩
        self.minsize(800, 700)
        
        # 设置外观模式和主题
        ctk.set_appearance_mode("System")  # "System", "Dark", "Light"
        ctk.set_default_color_theme("blue")  # "blue", "green", "dark-blue"

        # 配置网格布局
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(4, weight=1)  # 让输出区域可以扩展

        # 初始化变量
        self.selected_folder = ctk.StringVar()
        self.output_path = ctk.StringVar()
        self.scale_var = ctk.DoubleVar(value=0.98)
        self.auto_open_var = ctk.BooleanVar(value=True)
        self.output_path_locked = False  # 新增：输出路径锁定标志

        self.setup_ui()
    
    def center_window(self):
        """将窗口居中显示在屏幕中央，避免被任务栏遮挡"""
        self.update_idletasks()
        # 获取屏幕尺寸
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()
        # 估算任务栏高度（如40px），可根据实际需要调整
        taskbar_height = 70
        window_width = 1200  # 设定窗口宽度
        window_height = screen_height - taskbar_height  # 高度减去任务栏高度，避免被遮挡
        center_x = int((screen_width - window_width) / 2)
        center_y = 0
        self.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')

    def setup_ui(self):
        """设置用户界面"""
        
        # 统一字体为微软雅黑
        default_font = "Microsoft YaHei"

        # --- 1. 标题区域 ---
        title_frame = ctk.CTkFrame(self)
        title_frame.grid(row=0, column=0, padx=20, pady=(15, 10), sticky="ew")
        title_frame.grid_columnconfigure(0, weight=1)

        # 主标题
        title_label = ctk.CTkLabel(
            title_frame,
            text="🖼️ 图片转PPT工具",
            font=ctk.CTkFont(family=default_font, size=32, weight="bold")
        )
        title_label.grid(row=0, column=0, pady=(18, 7))

        # 副标题
        subtitle_label = ctk.CTkLabel(
            title_frame,
            text="将文件夹中的所有图片批量转换为精美的PPT演示文稿",
            font=ctk.CTkFont(family=default_font, size=16),
            text_color=("gray60", "gray40")
        )
        subtitle_label.grid(row=1, column=0, pady=(0, 18))

        # --- 2. 文件夹选择区域 ---
        folder_frame = ctk.CTkFrame(self)
        folder_frame.grid(row=1, column=0, padx=20, pady=(0, 8), sticky="ew")
        for i in range(4):
            folder_frame.grid_columnconfigure(i, weight=0)
        folder_frame.grid_columnconfigure(1, weight=1)  # 让Entry可拉伸

        # 统一标签字体
        folder_label = ctk.CTkLabel(
            folder_frame,
            text="📁 选择图片来源:",
            font=ctk.CTkFont(family=default_font, size=16, weight="bold")
        )
        folder_label.grid(row=0, column=0, padx=15, pady=(18, 10), sticky="w")

        self.path_entry = ctk.CTkEntry(
            folder_frame,
            textvariable=self.selected_folder,
            placeholder_text="请选择包含图片的文件夹...",
            font=ctk.CTkFont(family=default_font, size=16),
            height=40,
            width=400,
            state="readonly"
        )
        self.path_entry.grid(row=0, column=1, padx=(0, 8), pady=(18, 10), sticky="ew")

        self.browse_file_button = ctk.CTkButton(
            folder_frame,
            text="🖼️ 选择图片文件",
            command=self.browse_images,
            font=ctk.CTkFont(family=default_font, size=16, weight="bold"),
            height=40,
            width=140
        )
        self.browse_file_button.grid(row=0, column=2, padx=(0, 8), pady=(18, 10), sticky="ew")

        self.browse_folder_button = ctk.CTkButton(
            folder_frame,
            text="📁 选择图片文件夹",
            command=self.browse_folder,
            font=ctk.CTkFont(family=default_font, size=16, weight="bold"),
            height=40,
            width=140
        )
        self.browse_folder_button.grid(row=0, column=3, padx=(0, 15), pady=(18, 10), sticky="ew")

        # 文件夹信息标签
        self.info_label = ctk.CTkLabel(
            folder_frame,
            text="\U0001F4A1 请选择包含图片的文件夹（支持JPG、PNG、BMP、GIF、TIFF、WEBP格式），保证选用的图片都位于同一个文件夹下",
            font=ctk.CTkFont(family=default_font, size=13),
            text_color=("gray60", "gray40")
        )
        self.info_label.grid(row=1, column=0, columnspan=4, padx=15, pady=(0, 18))

        # --- PPT输出路径区域 ---
        output_path_label = ctk.CTkLabel(
            folder_frame,
            text="\U0001F4C2 PPT输出路径:",
            font=ctk.CTkFont(family=default_font, size=16, weight="bold")
        )
        output_path_label.grid(row=2, column=0, padx=15, pady=(0, 10), sticky="w")

        self.output_entry = ctk.CTkEntry(
            folder_frame,
            textvariable=self.output_path,
            font=ctk.CTkFont(family=default_font, size=16),
            height=40,
            width=400,
            state="readonly"
        )
        self.output_entry.grid(row=2, column=1, padx=(0, 8), pady=(0, 10), sticky="ew")

        self.choose_output_button = ctk.CTkButton(
            folder_frame,
            text="选择路径",
            command=self.choose_output_path,
            font=ctk.CTkFont(family=default_font, size=16, weight="bold"),
            height=40,
            width=140
        )
        self.choose_output_button.grid(row=2, column=2, padx=(0, 8), pady=(0, 10), sticky="ew")

        output_blank = ctk.CTkLabel(folder_frame, text="", width=140)
        output_blank.grid(row=2, column=3, padx=(0, 15), pady=(0, 10), sticky="ew")

        # --- 3. 设置选项区域 ---
        settings_frame = ctk.CTkFrame(self)
        settings_frame.grid(row=2, column=0, padx=20, pady=(0, 8), sticky="ew")
        settings_frame.grid_columnconfigure(1, weight=1)

        settings_label = ctk.CTkLabel(
            settings_frame,
            text="⚙️ 转换设置:",
            font=ctk.CTkFont(family=default_font, size=18, weight="bold")
        )
        settings_label.grid(row=0, column=0, columnspan=4, padx=15, pady=(18, 12), sticky="w")

        # 图片填充比例
        scale_label = ctk.CTkLabel(
            settings_frame,
            text="📏 图片填充比例:",
            font=ctk.CTkFont(family=default_font, size=15, weight="bold")
        )
        scale_label.grid(row=1, column=0, padx=15, pady=10, sticky="w")

        self.scale_slider = ctk.CTkSlider(
            settings_frame,
            from_=0.80,
            to=1.0,
            variable=self.scale_var,
            command=self.update_scale_label,
            number_of_steps=20,
            width=200
        )
        self.scale_slider.grid(row=1, column=1, padx=8, pady=10, sticky="ew")

        self.scale_value_label = ctk.CTkLabel(
            settings_frame,
            text=f"{self.scale_var.get():.2f}",
            font=ctk.CTkFont(family=default_font, size=15, weight="bold"),
            width=50
        )
        self.scale_value_label.grid(row=1, column=2, padx=8, pady=10)

        scale_info_label = ctk.CTkLabel(
            settings_frame,
            text="(0.8=留较多边距, 1.0=完全填满)",
            font=ctk.CTkFont(family=default_font, size=12),
            text_color=("gray60", "gray40")
        )
        scale_info_label.grid(row=1, column=3, padx=(0, 15), pady=10, sticky="w")

        # 自动打开选项
        self.auto_open_checkbox = ctk.CTkCheckBox(
            settings_frame,
            text="转换完成后自动打开PPT文件",
            variable=self.auto_open_var,
            font=ctk.CTkFont(family=default_font, size=15)
        )
        self.auto_open_checkbox.grid(row=2, column=0, columnspan=4, padx=15, pady=(0, 18), sticky="w")

        # --- 4. 转换按钮区域 ---
        button_frame = ctk.CTkFrame(self, fg_color="transparent")
        button_frame.grid(row=3, column=0, padx=20, pady=10, sticky="ew")
        button_frame.grid_columnconfigure((0, 1, 2), weight=1)

        self.clear_button = ctk.CTkButton(
            button_frame,
            text="🗑️ 清空",
            command=self.clear_fields,
            font=ctk.CTkFont(family=default_font, size=18, weight="bold"),
            height=50,
            fg_color=("#EF4444", "#B91C1C"),  # 红色
            hover_color=("#B91C1C", "#EF4444")
        )
        self.clear_button.grid(row=0, column=0, padx=4, pady=6, sticky="ew")

        self.copy_button = ctk.CTkButton(
            button_frame,
            text="📋 复制路径",
            command=self.copy_path_to_clipboard,
            font=ctk.CTkFont(family=default_font, size=18, weight="bold"),
            height=50,
            fg_color=("#3B82F6", "#2563EB"),  # 蓝色
            hover_color=("#2563EB", "#1D4ED8")
        )
        self.copy_button.grid(row=0, column=1, padx=4, pady=6, sticky="ew")

        self.convert_button = ctk.CTkButton(
            button_frame,
            text="🚀 开始转换",
            command=self.start_conversion,
            font=ctk.CTkFont(family=default_font, size=18, weight="bold"),
            height=50,
            fg_color=("#22C55E", "#15803D"),  # 绿色
            hover_color=("#15803D", "#22C55E"),
            state="disabled"
        )
        self.convert_button.grid(row=0, column=2, padx=4, pady=6, sticky="ew")

        # --- 5. 进度和状态区域 ---
        progress_frame = ctk.CTkFrame(self)
        progress_frame.grid(row=4, column=0, padx=20, pady=(0, 18), sticky="nsew")
        progress_frame.grid_columnconfigure(0, weight=1)
        progress_frame.grid_rowconfigure(2, weight=1)

        progress_title = ctk.CTkLabel(
            progress_frame,
            text="📊 转换进度:",
            font=ctk.CTkFont(family=default_font, size=18, weight="bold")
        )
        progress_title.grid(row=0, column=0, padx=15, pady=(18, 10), sticky="w")

        # 进度条
        self.progress_bar = ctk.CTkProgressBar(
            progress_frame,
            width=500,
            height=20,
            progress_color=("blue", "lightblue")
        )
        self.progress_bar.grid(row=1, column=0, padx=15, pady=(0, 10), sticky="ew")
        self.progress_bar.set(0)

        # 状态文本区域
        self.status_textbox = ctk.CTkTextbox(
            progress_frame,
            font=ctk.CTkFont(family=default_font, size=18),
            wrap="word",
            height=300  # 增加高度，确保内容完整显示
        )
        self.status_textbox.grid(row=2, column=0, padx=15, pady=(0, 18), sticky="nsew")
        self.status_textbox.insert("0.0", "🎯 就绪状态\n等待选择文件夹开始转换...\n\n💡 支持的图片格式：\n• JPG/JPEG\n• PNG\n• BMP\n• GIF\n• TIFF\n• WEBP")
        self.status_textbox.configure(state="disabled")

    def update_scale_label(self, value):
        """更新缩放比例标签"""
        self.scale_value_label.configure(text=f"{float(value):.2f}")

    def browse_images(self):
        """浏览图片文件（可多选）"""
        filetypes = [
            ("图片文件", "*.jpg *.jpeg *.png *.bmp *.gif *.tiff *.webp"),
            ("所有文件", "*.*")
        ]
        image_paths = filedialog.askopenfilenames(
            title="选择图片文件（可多选）",
            filetypes=filetypes,
            initialdir=os.path.expanduser("~")
        )
        if image_paths:
            self.selected_folder.set(os.path.dirname(image_paths[0]))
            self.selected_images = list(image_paths)
            self.check_selected_images()

    def browse_folder(self):
        """浏览图片文件夹"""
        folder_path = filedialog.askdirectory(
            title="选择包含图片的文件夹",
            initialdir=os.path.expanduser("~")
        )
        if folder_path:
            self.selected_folder.set(folder_path)
            self.selected_images = None
            self.check_folder_contents(folder_path)

    def check_selected_images(self):
        """检查用户直接选择的图片文件列表"""
        valid_images = []
        for img_path in self.selected_images:
            try:
                with Image.open(img_path) as im:
                    im.verify()
                valid_images.append(img_path)
            except Exception as e:
                self.log_status(f"⚠️ 跳过损坏或不支持的图片: {os.path.basename(img_path)} ({e})")
        image_count = len(valid_images)
        if image_count > 0:
            self.info_label.configure(
                text=f"✅ 已选择 {image_count} 张图片，准备就绪！",
                text_color=("green", "lightgreen")
            )
            self.convert_button.configure(
                state="normal",
                fg_color=("green", "darkgreen")
            )
            folder_name = os.path.basename(os.path.dirname(valid_images[0]))
            output_name = f"{folder_name}_图片合集.pptx"
            output_path = os.path.join(os.path.dirname(valid_images[0]), output_name)
            if not self.output_path_locked:
                self.output_path.set(output_path)
            self.valid_images = valid_images
            self.log_status(f"✅ 已选择 {image_count} 张图片")
            self.log_status(f"📁 输出文件: {output_name}")
        else:
            self.info_label.configure(
                text="❌ 没有可用图片文件",
                text_color=("red", "lightcoral")
            )
            self.convert_button.configure(
                state="disabled",
                fg_color="gray"
            )
            self.log_status("❌ 未找到任何可用图片文件")

    def check_folder_contents(self, folder_path):
        """检查文件夹内容，提前过滤损坏或不支持的图片"""
        try:
            self.log_status(f"🔍 正在扫描文件夹: {os.path.basename(folder_path)}")
            image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp', '*.gif', '*.tiff', '*.webp']
            image_files = []
            for extension in image_extensions:
                image_files.extend(glob.glob(os.path.join(folder_path, extension)))
                image_files.extend(glob.glob(os.path.join(folder_path, extension.upper())))
            image_files = list(set(image_files))
            valid_images = []
            for img_path in image_files:
                try:
                    with Image.open(img_path) as im:
                        im.verify()  # PIL验证图片
                    valid_images.append(img_path)
                except Exception as e:
                    self.log_status(f"⚠️ 跳过损坏或不支持的图片: {os.path.basename(img_path)} ({e})")
            image_count = len(valid_images)
            if image_count > 0:
                self.info_label.configure(
                    text=f"✅ 找到 {image_count} 张可用图片，准备就绪！",
                    text_color=("green", "lightgreen")
                )
                self.convert_button.configure(
                    state="normal",
                    fg_color=("green", "darkgreen")
                )
                folder_name = os.path.basename(folder_path)
                output_name = f"{folder_name}_图片合集.pptx"
                output_path = os.path.join(folder_path, output_name)
                if not self.output_path_locked:
                    self.output_path.set(output_path)
                self.valid_images = valid_images  # 保存可用图片列表
                self.log_status(f"✅ 扫描完成！找到 {image_count} 张可用图片")
                self.log_status(f"📁 输出文件: {output_name}")
            else:
                self.info_label.configure(
                    text="❌ 该文件夹中没有可用图片文件",
                    text_color=("red", "lightcoral")
                )
                self.convert_button.configure(
                    state="disabled",
                    fg_color="gray"
                )
                self.log_status("❌ 未找到任何可用图片文件")
        except Exception as e:
            self.info_label.configure(
                text=f"❌ 检查文件夹时出错: {str(e)}",
                text_color=("red", "lightcoral")
            )
            self.convert_button.configure(
                state="disabled",
                fg_color="gray"
            )
            self.log_status(f"❌ 错误: {str(e)}")

    def start_conversion(self):
        """开始转换（在后台线程中执行）"""
        if not self.selected_folder.get():
            messagebox.showwarning("警告", "请先选择一个文件夹！")
            return
            
        # 禁用转换按钮
        self.convert_button.configure(
            state="disabled", 
            text="🔄 转换中...",
            fg_color="gray"
        )
        self.progress_bar.set(0)
        
        self.log_status("🚀 开始转换流程...")
        
        # 在后台线程中执行转换
        thread = threading.Thread(target=self.convert_images_to_ppt)
        thread.daemon = True
        thread.start()

    def convert_images_to_ppt(self):
        """转换图片到PPT（后台执行，全部转为PNG临时文件再插入）"""
        import tempfile
        try:
            folder_path = self.selected_folder.get()
            output_file = self.output_path.get()
            scale_ratio = self.scale_var.get()
            # 优先用提前过滤的图片列表
            image_files = getattr(self, 'valid_images', None)
            if image_files is None:
                # 兼容老逻辑
                image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp', '*.gif', '*.tiff', '*.webp']
                image_files = []
                for extension in image_extensions:
                    image_files.extend(glob.glob(os.path.join(folder_path, extension)))
                    image_files.extend(glob.glob(os.path.join(folder_path, extension.upper())))
                image_files = list(set(image_files))
            image_files.sort()
            if not image_files:
                self.after(0, lambda: self.show_error("❌ 没有找到图片文件！"))
                return
            total_images = len(image_files)
            self.after(0, lambda: self.log_status(f"📊 找到 {total_images} 张图片，开始创建PPT..."))
            prs = Presentation()
            blank_slide_layout = prs.slide_layouts[6]
            for i, image_file in enumerate(image_files):
                try:
                    progress = (i + 1) / total_images
                    filename = os.path.basename(image_file)
                    self.after(0, lambda p=progress, f=filename: self.update_progress(p, f"🖼️ 正在处理: {f}"))
                    slide = prs.slides.add_slide(blank_slide_layout)
                    with Image.open(image_file) as img:
                        img = img.convert('RGB')  # 统一转为RGB，防止CMYK等异常
                        img_width, img_height = img.size
                        slide_width = prs.slide_width
                        slide_height = prs.slide_height
                        width_ratio = slide_width / img_width
                        height_ratio = slide_height / img_height
                        ratio = min(width_ratio, height_ratio) * scale_ratio
                        new_width = int(img_width * ratio)
                        new_height = int(img_height * ratio)
                        left = (slide_width - new_width) // 2
                        top = (slide_height - new_height) // 2
                        # 保存为临时PNG
                        with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:
                            temp_png = tmp.name
                            img.save(temp_png, format='PNG')
                        # 插入PPT
                        try:
                            pic = slide.shapes.add_picture(
                                temp_png,
                                left, top,
                                new_width, new_height
                            )
                        finally:
                            os.remove(temp_png)
                except Exception as e:
                    self.after(0, lambda f=filename, err=str(e): self.log_status(f"⚠️ 跳过文件 {f}: {err}"))
                    continue
            self.after(0, lambda: self.log_status("💾 正在保存PPT文件..."))
            prs.save(output_file)
            self.after(0, lambda: self.conversion_completed(output_file, len(prs.slides)))
        except Exception as e:
            self.after(0, lambda: self.show_error(f"转换过程中出错: {str(e)}"))

    def update_progress(self, progress, status_text):
        """更新进度条和状态"""
        self.progress_bar.set(progress)
        self.log_status(status_text)

    def conversion_completed(self, output_file, slide_count):
        """转换完成"""
        self.progress_bar.set(1.0)
        self.log_status(f"🎉 转换完成！")
        self.log_status(f"📊 共创建 {slide_count} 页精美幻灯片")
        self.log_status(f"📁 文件位置: {output_file}")
        
        # 恢复转换按钮
        self.convert_button.configure(
            state="normal", 
            text="🚀 开始转换",
            fg_color=("green", "darkgreen")
        )
        
        if self.auto_open_var.get():
            # 勾选时弹出“是否现在打开文件”对话框
            result = messagebox.askquestion(
                "🎉 转换完成",
                f"PPT文件已成功创建！\n\n📁 文件位置: {output_file}\n📊 幻灯片数量: {slide_count}页\n\n是否现在打开文件？",
                icon='question'
            )
            if result == 'yes':
                self.open_ppt_file(output_file)
        else:
            # 未勾选时弹出与勾选时相同的对话框，但只有“确定”按钮
            messagebox.showinfo(
                "🎉 转换完成",
                f"PPT文件已成功创建！\n\n📁 文件位置: {output_file}\n📊 幻灯片数量: {slide_count}页"
            )
        
    def open_ppt_file(self, file_path):
        """打开PPT文件"""
        try:
            if os.path.exists(file_path):
                if sys.platform == 'win32':
                    os.startfile(file_path)
                elif sys.platform == 'darwin':
                    subprocess.run(['open', file_path])
                else:
                    subprocess.run(['xdg-open', file_path])
                    
                self.log_status("✅ PPT文件已成功打开")
            else:
                messagebox.showerror("❌ 错误", f"文件不存在: {file_path}")
        except Exception as e:
            messagebox.showerror("❌ 错误", f"打开文件时出错: {str(e)}")

    def show_error(self, message):
        """显示错误消息"""
        self.log_status(f"❌ 转换失败: {message}")
        self.convert_button.configure(
            state="normal", 
            text="🚀 开始转换",
            fg_color=("green", "darkgreen")
        )
        messagebox.showerror("❌ 错误", message)

    def log_status(self, message):
        """记录状态信息"""
        self.status_textbox.configure(state="normal")
        self.status_textbox.insert("end", f"\n{message}")
        self.status_textbox.see("end")
        self.status_textbox.configure(state="disabled")

    def copy_path_to_clipboard(self):
        """复制输出路径到剪贴板"""
        if self.output_path.get():
            self.clipboard_clear()
            self.clipboard_append(self.output_path.get())
            messagebox.showinfo("📋 已复制", "输出路径已复制到剪贴板！")
            self.log_status("📋 输出路径已复制到剪贴板")
        else:
            messagebox.showwarning("⚠️ 警告", "请先选择一个文件夹！")

    def clear_fields(self):
        """清空所有字段"""
        self.selected_folder.set("")
        self.output_path.set("")
        self.output_path_locked = False  # 清空时解锁
        self.progress_bar.set(0)
        self.convert_button.configure(
            state="disabled",
            text="🚀 开始转换",
            fg_color="gray"
        )
        self.info_label.configure(
            text="💡 请选择包含图片的文件夹（支持JPG、PNG、BMP、GIF、TIFF、WEBP格式）",
            text_color=("gray60", "gray40")
        )
        
        # 清空状态文本
        self.status_textbox.configure(state="normal")
        self.status_textbox.delete("0.0", "end")
        self.status_textbox.insert("0.0", "🎯 就绪状态\n等待选择文件夹开始转换...\n\n💡 支持的图片格式：\n• JPG/JPEG\n• PNG\n• BMP\n• GIF\n• TIFF\n• WEBP")
        self.status_textbox.configure(state="disabled")
        
        self.log_status("🗑️ 已清空所有设置")

    def choose_output_path(self):
        """弹出保存对话框选择PPT输出路径"""
        initialdir = self.selected_folder.get() or os.path.expanduser("~")
        default_name = self.output_path.get() or "图片合集.pptx"
        file_path = filedialog.asksaveasfilename(
            title="选择PPT输出路径",
            defaultextension=".pptx",
            filetypes=[("PPTX文件", "*.pptx")],
            initialdir=initialdir,
            initialfile=os.path.basename(default_name)
        )
        if file_path:
            self.output_path.set(file_path)
            self.output_path_locked = True  # 用户手动选择后锁定

def main():
    # 设置控制台输出编码
    if sys.platform == 'win32':
        import locale
        try:
            sys.stdout.reconfigure(encoding='utf-8')
        except:
            pass
    
    # 创建应用
    app = PhotoToPPTApp()
    
    # 运行应用
    app.mainloop()

if __name__ == "__main__":
    main()
